package tictactoe.screens;

import java.awt.*;
import java.awt.Font.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;

import tictactoe.*;
import tictactoe.gameplay.*;
import tictactoe.types.*;

/**
 *
 * @author Ryan Falls
 */
public class GameScreen extends MenuScreen
{
	//Components
	private JLabel title;
	private JLabel[] cells;
	private JLabel[] filler;
	private Border hoverBorder;
	//Private variables
	private boolean gameState = false;
	private GameType gameType;
	/**
	 * Indicates whether it is currently the X Player's turn.
	 */
	private Boolean currentPlayerX = true;
	/**
	 * The current state of the Tic Tac Toe board.
	 */
	private CellState[] cellStates;
	private Player playerX;
	private Player playerO;

	//TODO Main Menu Screen
	public GameScreen(TicTacToe ttt)
	{
		this.ttt = ttt;
		setLayout(new GridBagLayout());

		GridBagConstraints constraints = new GridBagConstraints();
		hoverBorder = new LineBorder(Color.yellow, 8);

		//Add Title
	/*
		title = new JLabel(TicTacToe.TITLE);
		title.setFont(new Font("Serif",Font.PLAIN,50));
		constraints.gridx = 0;
		constraints.gridy = 0;
		constraints.gridheight = 1;
		constraints.gridwidth = 5;
		//constraints.insets = new Insets(10,10,5,10);
		constraints.fill = GridBagConstraints.NONE;
		add(title,constraints);
		 */

		//Create Filler
		filler = new JLabel[8];
		constraints.fill = GridBagConstraints.BOTH;
		for (int i = 0; i < 8; i++)
		{
			filler[i] = new JLabel();
			filler[i].setOpaque(true);
			filler[i].setForeground(Color.black);
			filler[i].setBackground(Color.black);
			if ((i % 3) == 2)
			{
				//Horizontal bars
				filler[i].setMinimumSize(new Dimension(208, 8));//64*3+8*2
				constraints.gridx = 0;
				constraints.gridy = (2 + (i / 3) * 2);
				constraints.gridwidth = 5;
			} else
			{
				//Vertical dividers
				filler[i].setMinimumSize(new Dimension(8, 64));
				constraints.gridx = ((i % 3) * 2 + 1);
				constraints.gridy = (1 + (i / 3) * 2);
				constraints.gridwidth = 1;
			}
			filler[i].setText("----");//constraints.gridy + "-" + constraints.gridx + "-" + constraints.gridwidth);
			add(filler[i], constraints);
		}

		//Create Cells
		cells = new JLabel[9];
		constraints.gridwidth = 1;
		constraints.fill = GridBagConstraints.NONE;
		for (int i = 0; i < 9; i++)
		{
			cells[i] = new JLabel();
			cells[i].setOpaque(true);
			cells[i].setForeground(Color.white);
			cells[i].setBorder(new EmptyBorder(8, 8, 8, 8));
			//Vertical dividers
			cells[i].setMinimumSize(new Dimension(64, 64));//.setSize(64, 64);
			cells[i].addMouseListener(new MouseAdapter()
			{
				@Override
				public void mouseEntered(MouseEvent e)
				{
					JLabel cell = (JLabel) (e.getComponent());
					GameScreen g = (GameScreen) (cell.getParent());
					cell.setBorder(g.hoverBorder);
				}

				@Override
				public void mouseExited(MouseEvent e)
				{
					JLabel cell = (JLabel) (e.getComponent());
					cell.setBorder(new EmptyBorder(8, 8, 8, 8));
				}

				@Override
				public void mouseClicked(MouseEvent e)
				{
					JLabel cell = (JLabel) (e.getComponent());
					GameScreen g = (GameScreen) (cell.getParent());
					if (gameType == GameType.Free)
					{
						g.MakeFreeMove(e.getButton(), cell);
					} else
					{
						g.MakeMove(cell);
					}
				}
			});
			constraints.gridx = ((i % 3) * 2);
			constraints.gridy = (1 + (i / 3) * 2);
			add(cells[i], constraints);
		}
	}

	/**
	 * Prepares the game board for a new game.
	 * @param type A GameType object indicating the type of game to start.
	 */
	public void NewGame(GameType type)
	{
		this.gameState = true;
		this.gameType = type;
		this.currentPlayerX = true;

		cellStates = new CellState[9];
		for (int i = 0; i < cellStates.length; i++)
		{
			cellStates[i] = CellState.NONE;
		}
		RedrawBoard();

		switch (type)
		{
			case Easy:
				playerX = new HumanPlayer(CellState.X);
				playerO = new EasyCPUPlayer(CellState.O);
				break;
			case Hard:
				playerX = new HumanPlayer(CellState.X);
				playerO = new HardCPUPlayer(CellState.O);
				break;
			case Multi:
				playerX = new HumanPlayer(CellState.X);
				playerO = new HumanPlayer(CellState.O);
				break;
			case Free:
				break;
			default:
				//TODO: Throw Error
				break;
		}
	}

	/**
	 * Used to detemine whether there is a currently active game.
	 * @return	True if there is a currently active game.
	 */
	public boolean GetGameState()
	{
		//Used to determine whether the MainMenu or AltMainMenu should be shown.
		return this.gameState;
	}

	/**
	 * Makes the next move on the tic-tac-toe board as determined
	 * by the cell passed into it.
	 * @param cell A JLabel indicating which cell was clicked.
	 */
	public void MakeMove(JLabel cell)
	{
		int targetCell = FindCell(cell);
		Player currentPlayer;
		int move;

		//Set the appropriate cell to the moving players icon.
		cellStates[targetCell] = currentPlayerX ? CellState.X : CellState.O;

		RedrawBoard();
		if (CheckGameOver())
		{
			return;
		}

		//Toggle the current player variable
		currentPlayerX = !currentPlayerX;

		//Get the new player
		currentPlayer = (currentPlayerX ? playerX : playerO);

		//Check if new player is computer, and if so, make move
		if (currentPlayer.isCPU())
		{
			move = currentPlayer.MakeMove(cellStates);
			if (move != -1)
			{
				cellStates[move] = currentPlayerX ? CellState.X : CellState.O;
				currentPlayerX = !currentPlayerX;
			} else
			{
				//TODO: Throw error
			}
		}

		RedrawBoard();
		CheckGameOver();

		return;
		//TODO: Throw error
	}

	/**
	 *
	 * @param mouseButton
	 * @param cell
	 */
	public void MakeFreeMove(int mouseButton, JLabel cell)
	{
		int targetCell = FindCell(cell);
		switch (mouseButton)
		{
			case MouseEvent.BUTTON1: //Left click
				ToggleCell(targetCell, CellState.X);
				break;
			case MouseEvent.BUTTON2: //Middle click
			case MouseEvent.BUTTON3: //Right click
				ToggleCell(targetCell, CellState.O);
				break;
			default:
				break;

		}
		RedrawBoard();
		CheckGameOver();
	}

	/**
	 * Searches the cellState array for the indicated cell.
	 * @param cell A JLabel that indicates the cell to look for.
	 * @return An int indicating the index in the cellState array where cell
	 * is located. Returns -1 if not found.
	 */
	public int FindCell(JLabel cell)
	{
		for (int i = 0; i < cells.length; i++)
		{
			if (cells[i].equals(cell))
			{
				return i;
			}
		}
		//Not found
		return -1;
		//TODO: Throw error?
	}

	/**
	 * Toggles the cell indicated by cellIndex with the CellState symbol.
	 * i.e. if the cell contains the indicated symbol, then the cell is set
	 * to none, otherwise it is set to symbol.
	 * @param cellIndex
	 * @param symbol
	 */
	private void ToggleCell(int cellIndex, CellState symbol)
	{
		cellStates[cellIndex] = ((cellStates[cellIndex] == symbol) ? CellState.NONE : symbol);
	}

	/**
	 * Checks to see if a player has won.
	 * If a player has won or the game is a draw, displays a message indicating
	 * the winner, and asks if the player wants to start a new game.
	 * @return Returns true if the game is over.
	 */
	private boolean CheckGameOver()
	{
		CellState currentGameState = CheckBoardForWin();
		String winner = "";
		Icon winnerIcon = null;
		if (currentGameState != null)
		{
			//Get winner
			switch (currentGameState)
			{
				case X:
					winner = "Player X Won!";
					winnerIcon = ttt.currentStyle.X;
					break;
				case O:
					winner = "Player O Won!";
					winnerIcon = ttt.currentStyle.O;
					break;
				case NONE:
					winner = "The game was a Draw!";
					winnerIcon = ttt.currentStyle.none;
					break;
				default:
				//TODO: Throw error
			}

			//Tell user about winner, and ask if he wants to play again
			switch (JOptionPane.showConfirmDialog(
					ttt, //Parent component
					winner + "\nPlay again?", //Message
					"Play again?", //Title bar text
					JOptionPane.YES_NO_OPTION, //Option type
					JOptionPane.PLAIN_MESSAGE, //Message type
					winnerIcon //Icon
					))
			{
				case JOptionPane.YES_OPTION:
					NewGame(gameType);
					break;
				case JOptionPane.NO_OPTION:
					gameState = false;
					ttt.ChangeScreen(Screens.mainMenu);
					ttt.MainMenu.SetAlternate(false);
					break;
				default:
					//TODO: Throw Error
					break;
			}
			//Game over
			return true;
		}

		//Game not over
		return false;
	}

	/**
	 * Searches the board for a winning move, and returns the
	 * @return A CellState enum value indicating the winner (CellState.NONE
	 * means a draw), or null if the game is not over.
	 */
	private CellState CheckBoardForWin()
	{
		for (int i = 0; i < 3; i++)
		{
			/*
			 * Check horizontal
			 * 012, 345, 678
			 */
			if (allEqual(cellStates[3 * i], cellStates[3 * i + 1], cellStates[3 * i + 2]))
			{
				return cellStates[3 * i];
			}
			/*
			 *Check vertical
			 * 036, 147, 258
			 */
			if (allEqual(cellStates[i], cellStates[i + 3], cellStates[i + 6]))
			{
				return cellStates[i];
			}
		}

		/*
		 * Check Diagonals
		 * [\12] || [01/]
		 * [3\5] || [3/5]
		 * [67\] || [/78]
		 * -048- or -642-
		 */
		//Check forward slant
		if (allEqual(cellStates[0], cellStates[4], cellStates[8]))
		{
			return cellStates[4];
		}
		//Check back slant
		if (allEqual(cellStates[2], cellStates[4], cellStates[6]))
		{
			return cellStates[4];
		}

		//Check for unmarked squares.
		for (int i = 0; i < cellStates.length; i++)
		{
			if (cellStates[i] == CellState.NONE)
			{
				//Found empty space, return unfinished
				return null;
			}
		}

		//Return draw.
		return CellState.NONE;
	}

	/**
	 * Tests to see whether all three cells are the same.
	 * @param cell1 The first cell to check
	 * @param cell2 The second cell to check
	 * @param cell3 The third cell to check
	 * @return True if all three cells are the same
	 */
	private boolean allEqual(CellState cell1, CellState cell2, CellState cell3)
	{
		return ((cell1 != CellState.NONE) && (cell1 == cell2) && (cell2 == cell3));
	}

	/**
	 * Redraws the board. Be sure to call this each time the cellStates change
	 * or the icons used to draw the board changes.
	 */
	public void RedrawBoard()
	{
		if ((ttt.currentStyle != null) && (cellStates != null))
		{
			for (int i = 0; i < 9; i++)
			{
				switch (cellStates[i])
				{
					case X:
						cells[i].setIcon(ttt.currentStyle.X);
						cells[i].setBackground(ttt.Options.GetPlayerXColor());
						break;
					case O:
						cells[i].setIcon(ttt.currentStyle.O);
						cells[i].setBackground(ttt.Options.GetPlayerOColor());
						break;
					case NONE:
						cells[i].setIcon(ttt.currentStyle.none);
						cells[i].setBackground(null);
						break;
					default:
						//TODO: Throw Error
						break;
				}
			}
		}
	}
}
